常量

  C++提供了用户定义常量的概念,const就是为了直接表述“不变化的值”这样一个概念。这种东西在一些环境中非常有用,例如,许多对象在初始化之后就不再改变自己的值了;与直接将文字量散步在代码中相比,采用符号常量写出的代码更容易维护;指针常常是边读边移动,而不是边写边移动;许多函数参数是只读不写的。

  关键字const可以加到一个对象的声明上,将这个对象声明为一个常量。因为不允许赋值,常量就必须进行初始化。例如,

    const int model = 90;            // model是个常量
    const int v[] = { 1, 2, 3, 4 };  // v[i]是常量
    const int x;                     // 错误❌:没有初始化

将某些东西声明为常量,就保证了在其作用域内不能改变它们的值:

    void f()
    {
        model = 200;        // 错误❌
        v[2]++;             // 错误❌
    }

请注意,const实际上改变了类型,也就是说,它限制了对象能够使用的方式,并没有描述常量应该如何分配。例如,

    void g(const X* p)
    {
        // 在这里不能修改 *p
    }

    void h()
    {
        X val;        // val可以被修改
        g(&val);
        // ...
    }

编译器可以以多种方式利用一个对象是常量的这一性质,当然,这实际上要看它有多么聪明。例如,对于常量的初始式常常是(也并不总是)常量表达式(C.5节);如果确实是这样,那么这个常量就可以在编译时进行求值。进一步说,如果编译器知道了某const的所有使用,它甚至可以不为该const分配空间。例如,

    const int c1 = 1;
    const int c2 = 2;
    const int c3 = my_f(3);        // 编译时不知道c3的值
    extern const int c4;           // 编译时不知道c4的值
    const int* p = &c2;            // 需要为c2分配空间

在这里,编译器能够知道c1和c2的值,所以就可以将这些值用到常量表达式里。由于在编译时无法知道c3和c4的值(仅仅通过位于这个编译单元中的信息,见9.1节),因此必须为c3和c4分配存储。又由于c2的地址被取用(而且应该假定它会在其他地方用),所以c2也需要分配存储。最简单的常见情况就是常量的值在编译时已知,而且不需要分配存储,c1就属于这种情况。关键字extern说明c4是在其他地方定义的(9.2节)。

  对于常量的数组,典型的情况是需要分配存储,因为一般说编译器无法弄清楚表达式里使用的是数组中的哪些元素。但无论如何,在许多机器上,甚至对这种情况也可以通过把常量的数组放进只读存储器里,并因此得到效率的改善。

  const的最常见用途是作为数组的界和作为分情况标号。例如,

    const int a = 42;
    const int b = 99;
    const int max = 128;
    int v[max];
    void f(int i)
    {
        switch(i)
        {
        case a:
            // ...
        case b:
            // ...
        }
    }

对于这类情况,人们也常用枚举符(4.8节)来代替const。

  关于const用到类成员函数上的使用方式将在第10.2.6节和第10.2.7节讨论。

  应该在程序里系统化地使用符号常量,以避免代码中“神秘的数值”。如果某个数值常量代码中反复出现,例如某个数组的界,要修改这些代码就可能变得很麻烦,因为要完成一次正确的更新,该常量的每一次出现都必须修改。采用符号常量可以使信息局部化。常见情况是,一个数值常量代表着程序中的一个假设。例如,4可能代表一个整数的字节数,128可能代表需要缓存的输入字符个数,6.24可能代表丹麦货币与美元的兑换比率。让这些数值常量散布在代码中,对程序的维护者而言这些值是很难辨认和理解的。常见的情况是,在程序移植时,或者当某些其他修改破坏了它们所表示的假设时,由于没有注意到这种数值,以致使它们变成了程序里的错误。将这种假设用加有良好注释的符号常量表示,就可以大大减少这一类维护问题。

🔚